package junior.util.compiler;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.BooleanUtils;


/**
 * Name: JavaStringCompiler
 * Function:
 *
 * @Author: K.K
 * Create Time: 2022/12/15
 * Modified By:
 * Modified Time:
 * Description:
 * Version:
 */
public class JavaStringCompiler {
    JavaCompiler compiler;
    StandardJavaFileManager stdManager;

    public JavaStringCompiler() {
        this.compiler = ToolProvider.getSystemJavaCompiler();
        this.stdManager = compiler.getStandardFileManager(null, null, null);
    }

    /**
     * Compile a Java source file in memory.
     *
     * @param name
     *            Java file name, e.g. "com.xxx.xxx.Test"
     * @param source
     *            The source code as String.
     * @param  options
     *            compiler options
     * @return The compiled results as Map that contains class name as key,
     *         class binary as value.
     * @throws IOException
     *             If compile error.
     */
    public Map<String, byte[]> compile(String name, String source, List<String> options) throws IOException {
        try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
            JavaFileObject javaFileObject = manager.makeStringSource(name, source);
            // List<String> options = Arrays.asList("-classpath", System.getProperty("java.class.path"), "-bootclasspath", System.getProperty("sun.boot.class.path"), "-extdirs", System.getProperty("java.ext.dirs"));
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, Collections.singletonList(javaFileObject));
            Boolean result = task.call();
            if (BooleanUtils.isNotTrue(result)) {
                throw new RuntimeException("Compilation failed.");
            }
            return manager.getClassBytes();
        }
    }

    /**
     * Load class from compiled classes.
     *
     * @param name
     *            Full class name.
     * @param classBytes
     *            Compiled results as a Map.
     * @return The Class instance.
     * @throws ClassNotFoundException
     *             If class not found.
     * @throws IOException
     *             If load error.
     */
    public Class<?> loadClass(String name, Map<String, byte[]> classBytes) throws ClassNotFoundException, IOException {
        try (MemoryClassLoader classLoader = new MemoryClassLoader(classBytes)) {
            return classLoader.loadClass(name);
        }
    }
}
